// This file is part of the AliceVision project.
// Copyright (c) 2016 AliceVision contributors.
// This Source Code Form is subject to the terms of the Mozilla Public License,
// v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.

#pragma once

#include <cstddef>
#include <iterator>
#include <memory>
#include <ostream>
#include <set>
#include <tuple>
#include <type_traits>
#include <unordered_set>
#include <utility>
#include <valarray>

/**
 * Copyright Louis Delacroix 2010 - 2014.
 * Distributed under the Boost Software License, Version 1.0.
 * (See http://www.boost.org/LICENSE_1_0.txt)
 */
namespace pretty_print {
namespace detail {
// SFINAE type trait to detect whether T::const_iterator exists.

struct sfinae_base
{
    using yes = char;
    using no = yes[2];
};

template<typename T>
struct has_const_iterator : private sfinae_base
{
  private:
    template<typename C>
    static yes& test(typename C::const_iterator*);
    template<typename C>
    static no& test(...);

  public:
    static const bool value = sizeof(test<T>(nullptr)) == sizeof(yes);
    using type = T;
};

template<typename T>
struct has_begin_end : private sfinae_base
{
  private:
#ifdef _MSC_VER
    // Work around MSVC ICE in 15.9.x by moving decltype out to template
    // typename
    template<typename C, typename LEFT = C::const_iterator (C::*)() const>
    static yes& f(typename std::enable_if<
                  std::is_same<decltype(static_cast<LEFT>(&C::begin)), typename C::const_iterator (C::*)() const>::value>::type*);
#else   // _MSCV_VER
    template<typename C>
    static yes& f(typename std::enable_if<std::is_same<decltype(static_cast<typename C::const_iterator (C::*)() const>(&C::begin)),
                                                       typename C::const_iterator (C::*)() const>::value>::type*);
#endif  // _MSCV_VER

    template<typename C>
    static no& f(...);

#ifdef _MSC_VER
    template<typename C, typename LEFT = C::const_iterator (C::*)() const>
    static yes& g(
      typename std::enable_if<std::is_same<decltype(static_cast<LEFT>(&C::end)), typename C::const_iterator (C::*)() const>::value,
                              void>::type*);
#else   // _MSCV_VER
    template<typename C>
    static yes& g(typename std::enable_if<std::is_same<decltype(static_cast<typename C::const_iterator (C::*)() const>(&C::end)),
                                                       typename C::const_iterator (C::*)() const>::value,
                                          void>::type*);
#endif  // _MSCV_VER

    template<typename C>
    static no& g(...);

  public:
    static bool const beg_value = sizeof(f<T>(nullptr)) == sizeof(yes);
    static bool const end_value = sizeof(g<T>(nullptr)) == sizeof(yes);
};

}  // namespace detail

// Holds the delimiter values for a specific character type

template<typename TChar>
struct delimiters_values
{
    using char_type = TChar;
    const char_type* prefix;
    const char_type* delimiter;
    const char_type* postfix;
};

// Defines the delimiter values for a specific container and character type

template<typename T, typename TChar>
struct delimiters
{
    using type = delimiters_values<TChar>;
    static const type values;
};

// Functor to print containers. You can use this directly if you want
// to specify a non-default delimiters type. The printing logic can
// be customized by specializing the nested template.

template<typename T, typename TChar = char, typename TCharTraits = ::std::char_traits<TChar>, typename TDelimiters = delimiters<T, TChar>>
struct print_container_helper
{
    using delimiters_type = TDelimiters;
    using ostream_type = std::basic_ostream<TChar, TCharTraits>;

    template<typename U>
    struct printer
    {
        static void print_body(const U& c, ostream_type& stream)
        {
            using std::begin;
            using std::end;

            auto it = begin(c);
            const auto the_end = end(c);

            if (it != the_end)
            {
                for (;;)
                {
                    stream << *it;

                    if (++it == the_end)
                        break;

                    if (delimiters_type::values.delimiter != NULL)
                        stream << delimiters_type::values.delimiter;
                }
            }
        }
    };

    print_container_helper(const T& container)
      : container_(container)
    {}

    inline void operator()(ostream_type& stream) const
    {
        if (delimiters_type::values.prefix != NULL)
            stream << delimiters_type::values.prefix;

        printer<T>::print_body(container_, stream);

        if (delimiters_type::values.postfix != NULL)
            stream << delimiters_type::values.postfix;
    }

  private:
    const T& container_;
};

// Specialization for pairs

template<typename T, typename TChar, typename TCharTraits, typename TDelimiters>
template<typename T1, typename T2>
struct print_container_helper<T, TChar, TCharTraits, TDelimiters>::printer<std::pair<T1, T2>>
{
    using ostream_type = typename print_container_helper<T, TChar, TCharTraits, TDelimiters>::ostream_type;

    static void print_body(const std::pair<T1, T2>& c, ostream_type& stream)
    {
        stream << c.first;
        if (print_container_helper<T, TChar, TCharTraits, TDelimiters>::delimiters_type::values.delimiter != NULL)
            stream << print_container_helper<T, TChar, TCharTraits, TDelimiters>::delimiters_type::values.delimiter;
        stream << c.second;
    }
};

// Specialization for tuples

template<typename T, typename TChar, typename TCharTraits, typename TDelimiters>
template<typename... Args>
struct print_container_helper<T, TChar, TCharTraits, TDelimiters>::printer<std::tuple<Args...>>
{
    using ostream_type = typename print_container_helper<T, TChar, TCharTraits, TDelimiters>::ostream_type;
    using element_type = std::tuple<Args...>;

    template<std::size_t I>
    struct Int
    {};

    static void print_body(const element_type& c, ostream_type& stream) { tuple_print(c, stream, Int<0>()); }

    static void tuple_print(const element_type&, ostream_type&, Int<sizeof...(Args)>) {}

    static void tuple_print(const element_type& c,
                            ostream_type& stream,
                            typename std::conditional<sizeof...(Args) != 0, Int<0>, std::nullptr_t>::type)
    {
        stream << std::get<0>(c);
        tuple_print(c, stream, Int<1>());
    }

    template<std::size_t N>
    static void tuple_print(const element_type& c, ostream_type& stream, Int<N>)
    {
        if (print_container_helper<T, TChar, TCharTraits, TDelimiters>::delimiters_type::values.delimiter != NULL)
            stream << print_container_helper<T, TChar, TCharTraits, TDelimiters>::delimiters_type::values.delimiter;

        stream << std::get<N>(c);

        tuple_print(c, stream, Int<N + 1>());
    }
};

// Prints a print_container_helper to the specified stream.

template<typename T, typename TChar, typename TCharTraits, typename TDelimiters>
inline std::basic_ostream<TChar, TCharTraits>& operator<<(std::basic_ostream<TChar, TCharTraits>& stream,
                                                          const print_container_helper<T, TChar, TCharTraits, TDelimiters>& helper)
{
    helper(stream);
    return stream;
}

// Basic is_container template; specialize to derive from std::true_type for all desired container types

template<typename T>
struct is_container
  : public std::integral_constant<bool,
                                  detail::has_const_iterator<T>::value && detail::has_begin_end<T>::beg_value && detail::has_begin_end<T>::end_value>
{};

template<typename T, std::size_t N>
struct is_container<T[N]> : std::true_type
{};

template<std::size_t N>
struct is_container<char[N]> : std::false_type
{};

template<typename T>
struct is_container<std::valarray<T>> : std::true_type
{};

template<typename T1, typename T2>
struct is_container<std::pair<T1, T2>> : std::true_type
{};

template<typename... Args>
struct is_container<std::tuple<Args...>> : std::true_type
{};

// Default delimiters

template<typename T>
struct delimiters<T, char>
{
    static const delimiters_values<char> values;
};
template<typename T>
const delimiters_values<char> delimiters<T, char>::values = {"[", ", ", "]"};
template<typename T>
struct delimiters<T, wchar_t>
{
    static const delimiters_values<wchar_t> values;
};
template<typename T>
const delimiters_values<wchar_t> delimiters<T, wchar_t>::values = {L"[", L", ", L"]"};

// Delimiters for (multi)set and unordered_(multi)set

template<typename T, typename TComp, typename TAllocator>
struct delimiters<::std::set<T, TComp, TAllocator>, char>
{
    static const delimiters_values<char> values;
};

template<typename T, typename TComp, typename TAllocator>
const delimiters_values<char> delimiters<::std::set<T, TComp, TAllocator>, char>::values = {"{", ", ", "}"};

template<typename T, typename TComp, typename TAllocator>
struct delimiters<::std::set<T, TComp, TAllocator>, wchar_t>
{
    static const delimiters_values<wchar_t> values;
};

template<typename T, typename TComp, typename TAllocator>
const delimiters_values<wchar_t> delimiters<::std::set<T, TComp, TAllocator>, wchar_t>::values = {L"{", L", ", L"}"};

template<typename T, typename TComp, typename TAllocator>
struct delimiters<::std::multiset<T, TComp, TAllocator>, char>
{
    static const delimiters_values<char> values;
};

template<typename T, typename TComp, typename TAllocator>
const delimiters_values<char> delimiters<::std::multiset<T, TComp, TAllocator>, char>::values = {"{", ", ", "}"};

template<typename T, typename TComp, typename TAllocator>
struct delimiters<::std::multiset<T, TComp, TAllocator>, wchar_t>
{
    static const delimiters_values<wchar_t> values;
};

template<typename T, typename TComp, typename TAllocator>
const delimiters_values<wchar_t> delimiters<::std::multiset<T, TComp, TAllocator>, wchar_t>::values = {L"{", L", ", L"}"};

template<typename T, typename THash, typename TEqual, typename TAllocator>
struct delimiters<::std::unordered_set<T, THash, TEqual, TAllocator>, char>
{
    static const delimiters_values<char> values;
};

template<typename T, typename THash, typename TEqual, typename TAllocator>
const delimiters_values<char> delimiters<::std::unordered_set<T, THash, TEqual, TAllocator>, char>::values = {"{", ", ", "}"};

template<typename T, typename THash, typename TEqual, typename TAllocator>
struct delimiters<::std::unordered_set<T, THash, TEqual, TAllocator>, wchar_t>
{
    static const delimiters_values<wchar_t> values;
};

template<typename T, typename THash, typename TEqual, typename TAllocator>
const delimiters_values<wchar_t> delimiters<::std::unordered_set<T, THash, TEqual, TAllocator>, wchar_t>::values = {L"{", L", ", L"}"};

template<typename T, typename THash, typename TEqual, typename TAllocator>
struct delimiters<::std::unordered_multiset<T, THash, TEqual, TAllocator>, char>
{
    static const delimiters_values<char> values;
};

template<typename T, typename THash, typename TEqual, typename TAllocator>
const delimiters_values<char> delimiters<::std::unordered_multiset<T, THash, TEqual, TAllocator>, char>::values = {"{", ", ", "}"};

template<typename T, typename THash, typename TEqual, typename TAllocator>
struct delimiters<::std::unordered_multiset<T, THash, TEqual, TAllocator>, wchar_t>
{
    static const delimiters_values<wchar_t> values;
};

template<typename T, typename THash, typename TEqual, typename TAllocator>
const delimiters_values<wchar_t> delimiters<::std::unordered_multiset<T, THash, TEqual, TAllocator>, wchar_t>::values = {L"{", L", ", L"}"};

// Delimiters for pair and tuple

template<typename T1, typename T2>
struct delimiters<std::pair<T1, T2>, char>
{
    static const delimiters_values<char> values;
};
template<typename T1, typename T2>
const delimiters_values<char> delimiters<std::pair<T1, T2>, char>::values = {"(", ", ", ")"};
template<typename T1, typename T2>
struct delimiters<::std::pair<T1, T2>, wchar_t>
{
    static const delimiters_values<wchar_t> values;
};
template<typename T1, typename T2>
const delimiters_values<wchar_t> delimiters<::std::pair<T1, T2>, wchar_t>::values = {L"(", L", ", L")"};

template<typename... Args>
struct delimiters<std::tuple<Args...>, char>
{
    static const delimiters_values<char> values;
};
template<typename... Args>
const delimiters_values<char> delimiters<std::tuple<Args...>, char>::values = {"(", ", ", ")"};
template<typename... Args>
struct delimiters<::std::tuple<Args...>, wchar_t>
{
    static const delimiters_values<wchar_t> values;
};
template<typename... Args>
const delimiters_values<wchar_t> delimiters<::std::tuple<Args...>, wchar_t>::values = {L"(", L", ", L")"};

// Type-erasing helper class for easy use of custom delimiters.
// Requires TCharTraits = std::char_traits<TChar> and TChar = char or wchar_t, and MyDelims needs to be defined for TChar.
// Usage: "cout << pretty_print::custom_delims<MyDelims>(x)".

struct custom_delims_base
{
    virtual ~custom_delims_base() {}
    virtual std::ostream& stream(::std::ostream&) = 0;
    virtual std::wostream& stream(::std::wostream&) = 0;
};

template<typename T, typename Delims>
struct custom_delims_wrapper : custom_delims_base
{
    custom_delims_wrapper(const T& t_)
      : t(t_)
    {}

    std::ostream& stream(std::ostream& s) { return s << print_container_helper<T, char, std::char_traits<char>, Delims>(t); }

    std::wostream& stream(std::wostream& s) { return s << print_container_helper<T, wchar_t, std::char_traits<wchar_t>, Delims>(t); }

  private:
    const T& t;
};

template<typename Delims>
struct custom_delims
{
    template<typename Container>
    custom_delims(const Container& c)
      : base(new custom_delims_wrapper<Container, Delims>(c))
    {}

    std::unique_ptr<custom_delims_base> base;
};

template<typename TChar, typename TCharTraits, typename Delims>
inline std::basic_ostream<TChar, TCharTraits>& operator<<(std::basic_ostream<TChar, TCharTraits>& s, const custom_delims<Delims>& p)
{
    return p.base->stream(s);
}

// A wrapper for a C-style array given as pointer-plus-size.
// Usage: std::cout << pretty_print_array(arr, n) << std::endl;

template<typename T>
struct array_wrapper_n
{
    typedef const T* const_iterator;
    typedef T value_type;

    array_wrapper_n(const T* const a, size_t n)
      : _array(a),
        _n(n)
    {}
    inline const_iterator begin() const { return _array; }
    inline const_iterator end() const { return _array + _n; }

  private:
    const T* const _array;
    size_t _n;
};

// A wrapper for hash-table based containers that offer local iterators to each bucket.
// Usage: std::cout << bucket_print(m, 4) << std::endl;  (Prints bucket 5 of container m.)

template<typename T>
struct bucket_print_wrapper
{
    typedef typename T::const_local_iterator const_iterator;
    typedef typename T::size_type size_type;

    const_iterator begin() const { return m_map.cbegin(n); }

    const_iterator end() const { return m_map.cend(n); }

    bucket_print_wrapper(const T& m, size_type bucket)
      : m_map(m),
        n(bucket)
    {}

  private:
    const T& m_map;
    const size_type n;
};

}  // namespace pretty_print

// Global accessor functions for the convenience wrappers

template<typename T>
inline pretty_print::array_wrapper_n<T> pretty_print_array(const T* const a, size_t n)
{
    return pretty_print::array_wrapper_n<T>(a, n);
}

template<typename T>
pretty_print::bucket_print_wrapper<T> bucket_print(const T& m, typename T::size_type n)
{
    return pretty_print::bucket_print_wrapper<T>(m, n);
}

// Main magic entry point: An overload snuck into namespace std.
// Can we do better?

namespace Eigen {
template<typename T>
class EigenBase;
}

namespace std {
// Prints a container to the stream using default delimiters
template<typename T>
struct is_eigen_object : std::is_base_of<Eigen::EigenBase<T>, T>
{};

template<typename T, typename TChar, typename TCharTraits>
inline typename enable_if<::pretty_print::is_container<T>::value && !is_eigen_object<T>::value, basic_ostream<TChar, TCharTraits>&>::type operator<<(
  basic_ostream<TChar, TCharTraits>& stream,
  const T& container)
{
    return stream << ::pretty_print::print_container_helper<T, TChar, TCharTraits>(container);
}
}  // namespace std
